package com.orange.wwwapi.service.sms;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.aliyun.sdk.service.dysmsapi20170525.AsyncClient;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsRequest;
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponse;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.orange.core.config.alicloud.sms.SmsProperties;
import com.orange.core.mapper.sms.SmsMapper;
import com.orange.core.pojo.entity.sms.Sms;
import com.orange.core.pojo.enums.ESmsChannel;
import com.orange.wwwapi.pojo.req.sms.CheckVerifyCodeReq;
import com.orange.wwwapi.pojo.req.sms.SendVerifyCodeReq;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.CompletableFuture;

@RequiredArgsConstructor
@Service
public class SmsService extends ServiceImpl<SmsMapper, Sms> {

    private final SmsProperties smsProperties;
    private final AsyncClient client;
    private final ObjectMapper objectMapper;

    /**
     * 发送验证码
     */
    @SneakyThrows
    @Transactional
    public boolean sendVerifyCode(SendVerifyCodeReq sendVerifyCodeReq) {
        if (StrUtil.isBlank(sendVerifyCodeReq.getCountryCode())) {
            sendVerifyCodeReq.setCountryCode("86");
        }

        // 该手机号所有验证码失效
        this.lambdaUpdate()
                .eq(Sms::getPhone, sendVerifyCodeReq.getPhone())
                .eq(Sms::getCountryCode, sendVerifyCodeReq.getCountryCode())
                .eq(Sms::getUsed, 0)
                .set(Sms::getUsed, 1)
                .update();

        String code = String.format("%06d", new Random().nextInt(999999));
        SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
                .templateCode(smsProperties.getVerifyCode().getTemplateCode())
                .templateParam(objectMapper.createObjectNode().put("code", code).toString())
                .signName(smsProperties.getVerifyCode().getSignatureName())
                .phoneNumbers(sendVerifyCodeReq.getPhone())
                .build();

        Sms sms = new Sms().setCountryCode(sendVerifyCodeReq.getCountryCode())
                .setPhone(sendVerifyCodeReq.getPhone())
                .setCode(code)
                .setUsed(0)
                .setChannel(ESmsChannel.YUN.getValue())
                .setExpireTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(System.currentTimeMillis() + smsProperties.getVerifyCode().getValidTimeMinutes() * 60000),
                        ZoneId.systemDefault()));
        this.save(sms);

        try {
            CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
            SendSmsResponse rsp = response.get();

            this.lambdaUpdate()
                    .eq(Sms::getId, sms.getId())
                    .set(Sms::getResponseMsg, objectMapper.writeValueAsString(rsp))
                    .update();
            return true;
        } catch (Exception e) {
            log.error("短信发送失败：", e);
            throw new RuntimeException("短信发送失败");
        }
    }

    /**
     * 校验手机验证码
     */
    @Transactional
    public boolean checkVerifyCode(CheckVerifyCodeReq req) {
        if (StrUtil.isBlank(req.getCountryCode())) {
            req.setCountryCode("86");
        }
        List<Sms> smsList = this.lambdaQuery()
                .eq(Sms::getPhone, req.getPhone())
                .eq(Sms::getCountryCode, req.getCountryCode())
                .eq(Sms::getUsed, 0)
                .gt(Sms::getExpireTime, LocalDateTime.now())
                .list();
        if (CollUtil.isNotEmpty(smsList)) {
            this.lambdaUpdate()
                    .eq(Sms::getCountryCode, req.getCountryCode())
                    .eq(Sms::getPhone, req.getPhone())
                    .set(Sms::getUsed, 1)
                    .update();
            return smsList.stream().map(Sms::getCode).anyMatch(code -> Objects.equals(code, req.getCode()));
        }
        return false;
    }
}
